#!/usr/bin/env python
# -*- coding: utf-8 -*-

import copy
import datetime
import sqlalchemy

import farado.logger
from farado.logger import logger

from farado.items.field_kind import ValueTypes



class RawQuerier:

    #--------------------------------------------------------------------------#
    def __init__(self, engine, metadata, session, mutex):
        self.engine = engine
        self.metadata = metadata
        self.session = session
        self.mutex = mutex

    #--------------------------------------------------------------------------#
    def issues_ids_by_id(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                sql_text = make_sql_text(
                    base_sql        = select_issues,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where_issue_id_like,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_issue_id,
                    desc_order_sql  = desc_order_by_issue_id,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_caption(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                value, where = _make_where_value(
                    where_issue_like,
                    where_issue_not_like,
                    where_issue_is_null,
                    value
                )
                sql_text = make_sql_text(
                    base_sql        = select_issues,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_issue,
                    desc_order_sql  = desc_order_by_issue,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_caption_or_content(
            self,
            value,
            is_order_asc=None):
        if not value:
            return []
        with self.mutex:
            with self.session.begin():
                sql_text = make_sql_text(
                    base_sql        = select_issues,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where_issue_caption_or_content_like,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_issue,
                    desc_order_sql  = desc_order_by_issue,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_kind(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                value, where = _make_where_value(
                    where_kind_like,
                    where_kind_not_like,
                    where_kind_is_null,
                    value
                )
                sql_text = make_sql_text(
                    base_sql        = select_issues_by_kind,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_kind,
                    desc_order_sql  = desc_order_by_kind,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_parent(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                value, where = _make_where_value(
                    where_sub_issue_like,
                    where_sub_issue_not_like,
                    where_sub_issue_is_null,
                    value
                )
                sql_text = make_sql_text(
                    base_sql        = select_issues_by_parent,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_sub_issue,
                    desc_order_sql  = desc_order_by_sub_issue,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_project(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                value, where = _make_where_value(
                    where_project_like,
                    where_project_not_like,
                    where_project_is_null,
                    value
                )
                sql_text = make_sql_text(
                    base_sql        = select_issues_by_project,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_project,
                    desc_order_sql  = desc_order_by_project,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_state(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                value, where = _make_where_value(
                    where_state_like,
                    where_state_not_like,
                    where_state_is_null,
                    value
                )
                sql_text = make_sql_text(
                    base_sql        = select_issues_by_state,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_state,
                    desc_order_sql  = desc_order_by_state,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_version(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                value, where = _make_where_value(
                    where_version_like,
                    where_version_not_like,
                    where_version_is_null,
                    value
                )
                sql_text = make_sql_text(
                    base_sql        = select_issues_by_version,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_version,
                    desc_order_sql  = desc_order_by_version,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_field(
            self,
            field_kind_id,
            value_type,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                sql_text = None
                if ValueTypes.issue_id == value_type:
                    value, where = _make_where_value(
                        where_sub_issue_like,
                        where_sub_issue_not_like,
                        where_sub_issue_is_null,
                        value
                    )
                    sql_text = make_sql_text(
                        base_sql        = select_issues_by_field_kind,
                        join_sql        = join_issues_by_field,
                        where_value     = value,
                        where_sql       = where,
                        order           = is_order_asc,
                        asc_order_sql   = asc_order_by_sub_issue,
                        desc_order_sql  = desc_order_by_sub_issue,
                    )
                elif ValueTypes.user_id == value_type:
                    value, where = _make_where_value(
                        where_user_like,
                        where_user_not_like,
                        where_user_is_null,
                        value
                    )
                    sql_text = make_sql_text(
                        base_sql        = select_issues_by_field_kind,
                        join_sql        = join_users_by_field,
                        where_value     = value,
                        where_sql       = where,
                        order           = is_order_asc,
                        asc_order_sql   = asc_order_by_user,
                        desc_order_sql  = desc_order_by_user,
                    )
                elif ValueTypes.project_id == value_type:
                    value, where = _make_where_value(
                        where_project_like,
                        where_project_not_like,
                        where_project_is_null,
                        value
                    )
                    sql_text = make_sql_text(
                        base_sql        = select_issues_by_field_kind,
                        join_sql        = join_projects_by_field,
                        where_value     = value,
                        where_sql       = where,
                        order           = is_order_asc,
                        asc_order_sql   = asc_order_by_project,
                        desc_order_sql  = desc_order_by_project,
                    )
                elif ValueTypes.version_id == value_type:
                    value, where = _make_where_value(
                        where_version_like,
                        where_version_not_like,
                        where_version_is_null,
                        value
                    )
                    sql_text = make_sql_text(
                        base_sql        = select_issues_by_field_kind,
                        join_sql        = join_versions_by_field,
                        where_value     = value,
                        where_sql       = where_version_like,
                        order           = is_order_asc,
                        asc_order_sql   = asc_order_by_version,
                        desc_order_sql  = desc_order_by_version,
                    )
                else:
                    value, where = _make_where_value(
                        where_value_like,
                        where_value_not_like,
                        where_value_is_null,
                        value
                    )
                    sql_text = make_sql_text(
                        base_sql        = select_issues_by_field_kind,
                        join_sql        = None,
                        where_value     = value,
                        where_sql       = where,
                        order           = is_order_asc,
                        asc_order_sql   = asc_order_by_value,
                        desc_order_sql  = desc_order_by_value,
                    )
                # TODO: Охватить типы данных: issues_ids, users_ids, projects_ids,
                #       versions_ids

                result = self.session.execute(sql_text,
                    {
                        'field_kind_id': field_kind_id,
                        'value': f'%{ value }%',
                    }
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_parent_id(self, parent_id):
        with self.mutex:
            with self.session.begin():
                sql_text = select_issues + ' WHERE issues.parent_id LIKE :value '
                result = self.session.execute(
                    sql_text,
                    {'value': f'{ parent_id }'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_project_id(self, project_id):
        with self.mutex:
            with self.session.begin():
                sql_text = select_issues + ' WHERE issues.project_id LIKE :value '
                result = self.session.execute(
                    sql_text,
                    {'value': f'{ project_id }'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_version_id(self, version_id):
        with self.mutex:
            with self.session.begin():
                sql_text = select_issues + ' WHERE issues.version_id LIKE :value '
                result = self.session.execute(
                    sql_text,
                    {'value': f'{ version_id }'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_created(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                sql_text = make_sql_text(
                    base_sql        = select_issues_by_created,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where_changes_like,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_changes,
                    desc_order_sql  = desc_order_by_changes,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_last_modified(
            self,
            value=None,
            is_order_asc=None):
        with self.mutex:
            with self.session.begin():
                sql_text = make_sql_text(
                    base_sql        = select_issues_by_last_modified,
                    join_sql        = None,
                    where_value     = value,
                    where_sql       = where_changes_like,
                    order           = is_order_asc,
                    asc_order_sql   = asc_order_by_changes,
                    desc_order_sql  = desc_order_by_changes,
                )
                result = self.session.execute(
                    sql_text,
                    {'value': f'%{ value }%'},
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issues_ids_by_modifier_user(self, user_id, from_date):
        with self.mutex:
            with self.session.begin():
                result = self.session.execute(
                    select_issues_by_user_modifier,
                    {
                        'user_id': str(user_id),
                        'from_date': from_date,
                    },
                )
                ids = [row[0] for row in result]
                # FIXME : Решить вопрос уникальности идентификаторов через
                # SQL-запрос GROUP BY, в котором есть неоднозначность
                # см. http://www.sqlite.org/releaselog/3_7_11.html
                # расширение greatest-n-per-group
                unique_ids = []
                for id in ids:
                    if id not in unique_ids:
                        unique_ids.append(id)
                return unique_ids

    #--------------------------------------------------------------------------#
    def issue_changes_ids_by_user_and_states(self, user_id, states_ids):
        with self.mutex:
            with self.session.begin():
                query = sqlalchemy.text(
                    select_issue_changes_by_user_and_states.format(
                        states_ids=tuple(states_ids)
                    )
                )
                result = self.session.execute(
                    query,
                    {
                        'user_id': str(user_id),
                    },
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issue_changes_ids_with_state_before_date(self, to_date):
        with self.mutex:
            with self.session.begin():
                result = self.session.execute(
                    select_issue_changes_states_before_date_time,
                    {
                        'to_date': to_date,
                    },
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def issue_changes_ids_with_state_before_date_by_project(
            self,
            to_date,
            project_id,
            ):
        with self.mutex:
            with self.session.begin():
                result = self.session.execute(
                    select_issue_changes_states_before_date_time_by_project,
                    {
                        'to_date': to_date,
                        'project_id': project_id,
                    },
                )
                return [row[0] for row in result]

    #--------------------------------------------------------------------------#
    def remove_old_messages(self, days_before_old=5):
        now = datetime.datetime.now()
        from_date = now.date() - datetime.timedelta(days=days_before_old)
        with self.mutex:
            with self.session.begin():
                self.session.execute(
                    delete_old_messages,
                    {
                        'from_date': from_date,
                    },
                )

    #--------------------------------------------------------------------------#
    def last_modified_project_date_time(
            self,
            project_id):
        with self.mutex:
            with self.session.begin():
                result = self.session.execute(
                    date_time_by_project_last_modified_issue,
                    {'project_id': project_id },
                )
                if result := [row[0] for row in result]:
                    return result[0].split('.')[0]
                return ''

    #--------------------------------------------------------------------------#
    def project_issues_count(
            self,
            project_id):
        with self.mutex:
            with self.session.begin():
                result = self.session.execute(
                    issues_count_by_project,
                    {'project_id': project_id },
                )
                if result := [row[0] for row in result]:
                    return int(result[0])
                return 0

    #--------------------------------------------------------------------------#
    def project_versions_count(
            self,
            project_id):
        with self.mutex:
            with self.session.begin():
                result = self.session.execute(
                    versions_count_by_project,
                    {'project_id': project_id },
                )
                if result := [row[0] for row in result]:
                    return int(result[0])
                return 0

#------------------------------------------------------------------------------#
def intersect(first, second):
    return [item for item in first if item in second]

#------------------------------------------------------------------------------#
def unite(first, second):
    result = copy.deepcopy(first)
    return result + [item for item in second if item not in result]

#------------------------------------------------------------------------------#
def make_sql_text(
        base_sql,
        join_sql=None,
        where_value=None,
        where_sql=None,
        order=None,
        asc_order_sql=None,
        desc_order_sql=None,
        ):
    result = base_sql
    if join_sql:
        result += join_sql
    if where_value and where_sql:
        result += where_sql
    if not order == None and asc_order_sql and desc_order_sql:
        result += asc_order_sql if order else desc_order_sql
    return result

#------------------------------------------------------------------------------#
def _make_where_value(
        where_like,
        where_not_like,
        where_is_null,
        value):
    where_result = where_like
    value_result = value
    if _is_empty_value(value):
        where_result = where_is_null
    elif _is_not_value(value):
        where_result = where_not_like
        value_result = value[1:]
    return (value_result, where_result)

#------------------------------------------------------------------------------#
def _is_not_value(value):
    if type(value) == str:
        return value.startswith("!")
    return False

#------------------------------------------------------------------------------#
def _is_empty_value(value):
    if type(value) == str:
        return bool(value == "!")
    return False

#------------------------------------------------------------------------------#
# Составные части SQL-запросов                                                 #
#------------------------------------------------------------------------------#

# -- issue -- #
#------------------------------------------------------------------------------#
select_issues = '''
SELECT
    issues.id
FROM
    issues
'''
select_issues_by_kind = select_issues + '''
LEFT JOIN issue_kinds
    ON issues.issue_kind_id == issue_kinds.id
'''
select_issues_by_parent = select_issues + '''
LEFT JOIN issues AS sub_issues
    ON issues.parent_id == sub_issues.id
'''
select_issues_by_project = select_issues + '''
LEFT JOIN projects
    ON issues.project_id == projects.id
'''
select_issues_by_state = select_issues + '''
LEFT JOIN states
    ON issues.state_id == states.id
'''
select_issues_by_version = select_issues + '''
LEFT JOIN versions
    ON issues.version_id == versions.id
'''
select_issues_by_field_kind = select_issues + '''
LEFT JOIN fields
    ON fields.issue_id == issues.id AND fields.field_kind_id == :field_kind_id
'''
where_issue_like = '''
WHERE
    issues.caption LIKE :value
'''
where_issue_not_like = '''
WHERE
    issues.caption NOT LIKE :value
'''
where_issue_is_null = '''
WHERE
    issues.caption IS NULL
'''
where_issue_id_like = '''
WHERE
    issues.id LIKE :value
'''
where_issue_caption_or_content_like = '''
WHERE
    issues.caption LIKE :value
OR
    issues.content LIKE :value
'''
asc_order_by_issue = ' ORDER BY issues.caption ASC '
desc_order_by_issue = ' ORDER BY issues.caption DESC '
asc_order_by_issue_id = ' ORDER BY issues.id ASC '
desc_order_by_issue_id = ' ORDER BY issues.id DESC '

# -- sub_issue -- #
#------------------------------------------------------------------------------#
join_issues_by_field = '''
LEFT JOIN issues AS sub_issues
    ON fields.value == sub_issues.id
'''
where_sub_issue_like = '''
WHERE
    sub_issues.caption LIKE :value
'''
where_sub_issue_not_like = '''
WHERE
    sub_issues.caption NOT LIKE :value
'''
where_sub_issue_is_null = '''
WHERE
    sub_issues.caption IS NULL
'''
asc_order_by_sub_issue = ' ORDER BY sub_issues.caption ASC '
desc_order_by_sub_issue = ' ORDER BY sub_issues.caption DESC '

# -- issue_kind -- #
#------------------------------------------------------------------------------#
where_kind_like = '''
WHERE
    issue_kinds.caption LIKE :value
'''
where_kind_not_like = '''
WHERE
    issue_kinds.caption NOT LIKE :value
'''
where_kind_is_null = '''
WHERE
    issue_kinds.caption IS NULL
'''
asc_order_by_kind = ' ORDER BY issue_kinds.caption ASC '
desc_order_by_kind = ' ORDER BY issue_kinds.caption DESC '

# -- project -- #
#------------------------------------------------------------------------------#
join_projects_by_field = '''
LEFT JOIN projects
    ON fields.value == projects.id
'''
where_project_like = '''
WHERE
    projects.caption LIKE :value
'''
where_project_not_like = '''
WHERE
    projects.caption NOT LIKE :value
'''
where_project_is_null = '''
WHERE
    projects.caption IS NULL
'''
asc_order_by_project = ' ORDER BY projects.caption ASC '
desc_order_by_project = ' ORDER BY projects.caption DESC '

# -- version -- #
#------------------------------------------------------------------------------#
join_versions_by_field = '''
LEFT JOIN versions
    ON fields.value == versions.id
'''
where_version_like = '''
WHERE
    versions.caption LIKE :value
'''
where_version_not_like = '''
WHERE
    versions.caption NOT LIKE :value
'''
where_version_is_null = '''
WHERE
    versions.caption IS NULL
'''
asc_order_by_version = ' ORDER BY versions.caption ASC '
desc_order_by_version = ' ORDER BY versions.caption DESC '

# -- user -- #
#------------------------------------------------------------------------------#
join_users_by_field = '''
LEFT JOIN users
    ON fields.value == users.id
'''
where_user_like = '''
WHERE
    users.last_name || ' ' || users.first_name LIKE :value
'''
where_user_not_like = '''
WHERE
    users.last_name || ' ' || users.first_name NOT LIKE :value
'''
where_user_is_null = '''
WHERE
    users.last_name || users.first_name IS NULL
'''
asc_order_by_user = ' ORDER BY users.last_name ASC, users.first_name ASC '
desc_order_by_user = ' ORDER BY users.last_name DESC, users.first_name DESC '

# -- value -- #
#------------------------------------------------------------------------------#
where_value_like = '''
WHERE
    fields.value LIKE :value
'''
where_value_not_like = '''
WHERE
    fields.value NOT LIKE :value
'''
where_value_is_null = '''
WHERE
    fields.value IS NULL
'''
asc_order_by_value = ' ORDER BY fields.value ASC '
desc_order_by_value = ' ORDER BY fields.value DESC '

# -- state -- #
#------------------------------------------------------------------------------#
where_state_like = '''
WHERE
    states.caption LIKE :value
'''
where_state_not_like = '''
WHERE
    states.caption NOT LIKE :value
'''
where_state_is_null = '''
WHERE
    states.caption IS NULL
'''
asc_order_by_state = ' ORDER BY states.caption ASC '
desc_order_by_state = ' ORDER BY states.caption DESC '

# -- changes -- #
#------------------------------------------------------------------------------#
select_issues_by_created = select_issues + '''
JOIN (
    SELECT
        MIN(issue_changes.date_time) AS date_time,
        issue_changes.issue_id
    FROM
        issue_changes
    GROUP BY
        issue_changes.issue_id
    ) AS changes
    ON issues.id == changes.issue_id
'''
select_issues_by_last_modified = select_issues + '''
JOIN (
    SELECT
        MAX(issue_changes.date_time) AS date_time,
        issue_changes.issue_id
    FROM
        issue_changes
    GROUP BY
        issue_changes.issue_id
    ) AS changes
    ON issues.id == changes.issue_id
'''
where_changes_like = '''
WHERE
    changes.date_time LIKE :value
'''
asc_order_by_changes = ' ORDER BY changes.date_time ASC '
desc_order_by_changes = ' ORDER BY changes.date_time DESC '

# -- user_statistics -- #
#------------------------------------------------------------------------------#
select_issues_by_user_modifier = select_issues + '''
JOIN (
    SELECT
        *
    FROM
        issue_changes
    ORDER BY
        issue_changes.date_time DESC
    ) AS changes
ON
    issues.id == changes.issue_id
WHERE
    changes.user_id == :user_id
AND
    changes.date_time > :from_date
ORDER BY
    changes.date_time DESC
'''

select_issue_changes_by_user_and_states = '''
SELECT
    issue_changes.id
FROM
    issue_changes
WHERE
    issue_changes.user_id == :user_id
AND
    issue_changes.issue_id IN (
    SELECT
        issues.id
    FROM
        issues
    WHERE
        issues.state_id IN {states_ids}
    )
ORDER BY
    issue_changes.date_time DESC
'''

select_issue_changes_states_before_date_time = r'''
SELECT
    issue_changes.id
FROM
    issue_changes
WHERE
    issue_changes.date_time < :to_date
AND
    issue_changes.issue_id IN (
        SELECT
            id
        FROM
            issues
        WHERE
            issues.id NOT IN (
            SELECT
                parent_id
            FROM
                issues
            WHERE
                parent_id is not NULL
        )
    )
AND
    issue_changes.diff REGEXP '^{\s*"3":.*'
ORDER BY
    issue_changes.date_time
'''

select_issue_changes_states_before_date_time_by_project = r'''
SELECT
    issue_changes.id
FROM
    issue_changes
WHERE
    issue_changes.date_time < :to_date
AND
    issue_changes.issue_id IN (
        SELECT
            id
        FROM
            issues
        WHERE
            issues.project_id == :project_id
        AND
            issues.id NOT IN (
            SELECT
                parent_id
            FROM
                issues
            WHERE
                parent_id is not NULL
        )
    )
AND
    issue_changes.diff REGEXP '^{\s*"3":.*'
ORDER BY
    issue_changes.date_time
'''

# -- messages -- #
#------------------------------------------------------------------------------#
delete_old_messages = '''
DELETE FROM
    messages
WHERE
    messages.id IN (
        SELECT
            messages.id
        FROM
            messages
        WHERE
            DATE(messages.date_time) < :from_date
    )
'''

# -- project data -- #
#------------------------------------------------------------------------------#
date_time_by_project_last_modified_issue = '''
SELECT
    changes.date_time
FROM
    issues
JOIN (
    SELECT
        MAX(issue_changes.date_time) AS date_time,
        issue_changes.issue_id
    FROM
        issue_changes
    GROUP BY
        issue_changes.issue_id
    ) AS changes
    ON issues.id == changes.issue_id
WHERE
    issues.project_id = :project_id
ORDER BY
    changes.date_time desc
LIMIT 1
'''
issues_count_by_project = '''
SELECT
    count(issues.id)
FROM
    issues
WHERE
    issues.project_id = :project_id
'''
versions_count_by_project = '''
SELECT
    count(versions.id)
FROM
    versions
WHERE
    versions.project_id = :project_id
'''
